#include "SemanticVisitor.h"
#include <iostream>

#include "Char.h"
#include "Command.h"
#include "Id.h"
#include "Index.h"
#include "Number.h"
#include "StatementList.h"
#include "String.h"

#include "Add.h"
#include "Sub.h"
#include "Mul.h"
#include "Div.h"
#include "Mod.h"
#include "Neg.h"
#include "Equ.h"

#include "DoubleEqual.h"
#include "NotEqual.h"
#include "Less.h"
#include "LessEqual.h"
#include "Greater.h"
#include "GreaterEqual.h"
#include "And.h"
#include "Or.h"

#include "While.h"
#include "If.h"

#include "FunctionCall.h"
#include "FunctionDefinition.h"
#include "Return.h"
#include "ParamNode.h"
#include "ArgNode.h"

using namespace std;


void SemanticVisitor::visitId( Id* ID )
{
	stk.push(sym->GetEType(ID->getId()));
}

void SemanticVisitor::visitNumber( Number* NUMBER )
{
	stk.push(SN);
}

void SemanticVisitor::visitIndex( Index* INDEX )
{
	INDEX->getIdentifier()->accept(*this);
	INDEX->getOffset()->accept(*this);

	etype offset = stk.top();
	stk.pop();
	etype identifier = stk.top();
	stk.pop();

	if( offset != SN )
	{
		cerr << "Error(line " << INDEX->getLineNum() << "): Non numeric type in index operator." << endl;
		exit(1);
	}
	if( identifier != AN && identifier != AC )
	{
		cerr << "Error(line " << INDEX->getLineNum() << "): Non array type used with index operator." << endl;
		exit(1);
	}

	switch( identifier )
	{
	case AN:
		stk.push(SN);
		break;
	case AC:
		stk.push(SC);
		break;
	default:
		break;
	}
}

void SemanticVisitor::visitChar( Char* CHAR )
{
	stk.push(SC);
}

void SemanticVisitor::visitString( String* STRING )
{
	stk.push(AC);
}

void SemanticVisitor::visitStatementList( StatementList* SL )
{
	for( list<Node*>::const_iterator it = SL->getStatementList().begin(); it != SL->getStatementList().end(); it++ )
	{
		(*it)->accept(*this);
	}
}

void SemanticVisitor::visitCommand( Command* COMMAND )
{
	if(COMMAND->getName() == "time")
	{
		if (! COMMAND->getExprList().empty()) // Time takes no arguments
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Incorrect number of arguments for function time.\n";
			exit(1);
		}
		stk.push(SN);
	}
	else if(COMMAND->getName() == "random")
	{
		if ( COMMAND->getExprList().size() != 1 ) // We need just one argument
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Incorrect number of arguments for function random.\n";
			exit(1);
		}
		list<Node*>::const_iterator it = COMMAND->getExprList().begin();
		(*it)->accept(*this);
		if( stk.top() != SN )
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Function random requires a numerical argument." << endl;
			exit(1);
		}
		stk.pop();
		stk.push(SN);
	}
	else if(COMMAND->getName() == "zeros")
	{
		if ( COMMAND->getExprList().size() != 1 ) // We need just one argument
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Incorrect number of arguments for function zeros.\n";
			exit(1);
		}
		list<Node*>::const_iterator it = COMMAND->getExprList().begin();
		(*it)->accept(*this);
		if( stk.top() != SN )
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Function zeros requires a numerical argument." << endl;
			exit(1);
		}
		stk.pop();
		stk.push(AN);
	}
	else if(COMMAND->getName() == "size")
	{
		if ( COMMAND->getExprList().size() != 1 ) // We need just one argument
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Incorrect number of arguments for function size.\n";
			exit(1);
		}
		list<Node*>::const_iterator it = COMMAND->getExprList().begin();
		(*it)->accept(*this);
		if( stk.top() != AN && stk.top() != AC )
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Function size requires an array as an argument." << endl;
			exit(1);
		}
		stk.pop();
		stk.push(SN);
	}
	else if(COMMAND->getName() == "resize")
	{
		if ( COMMAND->getExprList().size() != 2) // We need just two arguments
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Incorrect number of arguments for function resize.\n";
			exit(1);
		}
		for( list<Node*>::const_iterator it = COMMAND->getExprList().begin(); it != COMMAND->getExprList().end(); it++ )
		{
			(*it)->accept(*this);
		}
		etype second = stk.top();
		stk.pop();
		etype first = stk.top();
		stk.pop();

		if( first != AN && first != AC )
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Resize needs an array as the first argument." << endl;
			exit(1);
		}
		if ( second != SN )
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Resize needs a number as the second argument." << endl;
			exit(1);
		}
		stk.push(SN);
	}
	else if(COMMAND->getName() == "print")
	{
		// We will take as many arguments as they want to cram in
		for( list<Node*>::const_iterator it = COMMAND->getExprList().begin(); it != COMMAND->getExprList().end(); it++ )
		{
			(*it)->accept(*this);
			if( stk.top() == TYPE_LESS )
			{
				cerr << "Error(line " << COMMAND->getLineNum() << "): Can only print values that have types!" << endl;
				exit(1);
			}
		}

		stk.push(TYPE_LESS);
	}
	else if(COMMAND->getName() == "set_rotate") //void set_rotate(number angle);
	{
		if ( COMMAND->getExprList().size() != 1 ) // We need just one argument
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Incorrect number of arguments for function set_rotate.\n";
			exit(1);
		}
		list<Node*>::const_iterator it = COMMAND->getExprList().begin();
		(*it)->accept(*this);
		if( stk.top() != SN )
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Function set_rotate requires a numerical argument." << endl;
			exit(1);
		}
		stk.pop();

		stk.push(TYPE_LESS);
	}
	else if(COMMAND->getName() == "get_rotate") //number get_rotate();
	{
		if (! COMMAND->getExprList().empty())
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Incorrect number of arguments for function get_rotate.\n";
			exit(1);
		}
		stk.push(SN);
	}
	else if(COMMAND->getName() == "set_move") //void set_move(number distance);
	{
		if ( COMMAND->getExprList().size() != 1 ) // We need just one argument
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Incorrect number of arguments for function set_move.\n";
			exit(1);
		}
		list<Node*>::const_iterator it = COMMAND->getExprList().begin();
		(*it)->accept(*this);
		if( stk.top() != SN )
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Function set_move requires a numerical argument." << endl;
			exit(1);
		}
		stk.pop();
		stk.push(TYPE_LESS);
	}
	else if(COMMAND->getName() == "get_move") //number get_move();
	{
		if (! COMMAND->getExprList().empty())
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Incorrect number of arguments for function get_move.\n";
			exit(1);
		}
		stk.push(SN);
	}
	else if(COMMAND->getName() == "set_shields") //void set_shields(number energy);
	{
		if ( COMMAND->getExprList().size() != 1 ) // We need just one argument
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Incorrect number of arguments for function set_shields.\n";
			exit(1);
		}
		list<Node*>::const_iterator it = COMMAND->getExprList().begin();
		(*it)->accept(*this);
		if( stk.top() != SN )
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Function set_shields requires a numerical argument." << endl;
			exit(1);
		}
		stk.pop();
		stk.push(TYPE_LESS);
	}
	else if(COMMAND->getName() == "get_shields") //number get_shields();
	{
		if (! COMMAND->getExprList().empty())
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Incorrect number of arguments for function get_shields.\n";
			exit(1);
		}
		stk.push(SN);
	}
	else if(COMMAND->getName() == "fire") //void fire(number energy);
	{
		if ( COMMAND->getExprList().size() != 1 ) // We need just one argument
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Incorrect number of arguments for function fire.\n";
			exit(1);
		}
		list<Node*>::const_iterator it = COMMAND->getExprList().begin();
		(*it)->accept(*this);
		if( stk.top() != SN )
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Function fire requires a numerical argument." << endl;
			exit(1);
		}
		stk.pop();
		stk.push(TYPE_LESS);
	}
	else if(COMMAND->getName() == "get_energy") //number get_energy();
	{
		if (! COMMAND->getExprList().empty())
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Incorrect number of arguments for function get_energy.\n";
			exit(1);
		}
		stk.push(SN);
	}
	else if(COMMAND->getName() == "scan_distance") //number scan_distance();
	{
		if (! COMMAND->getExprList().empty())
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Incorrect number of arguments for function scan_distance.\n";
			exit(1);
		}
		stk.push(SN);
	}
	else if(COMMAND->getName() == "scan_angle") //number scan_angle();
	{
		if (! COMMAND->getExprList().empty())
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Incorrect number of arguments for function scan_angle.\n";
			exit(1);
		}
		stk.push(SN);
	}
	else if(COMMAND->getName() == "scan_id") //number scan_id();
	{
		if (! COMMAND->getExprList().empty())
		{
			cerr << "Error(line " << COMMAND->getLineNum() << "): Incorrect number of arguments for function scan_id.\n";
			exit(1);
		}
		stk.push(SN);
	}
}

void SemanticVisitor::visitNeg( Neg* NEG )
{
	NEG->getChild()->accept(*this);
	if( stk.top() != SN )
	{
		cerr << "Error(line " << NEG->getLineNum() << "): Negating non numeric type." << endl;
		exit(1);
	}
	stk.pop();
	stk.push(SN);
}

void SemanticVisitor::visitAdd( Add* ADD )
{
	ADD->getLeft()->accept(*this);
	ADD->getRight()->accept(*this);

	etype right = stk.top();
	stk.pop();
	etype left = stk.top();
	stk.pop();

	if( left != SN || right != SN )
	{
		cerr << "Error(line " << ADD->getLineNum() << "): Cannot add non numeric type." << endl;
		exit(1);
	}
	stk.push(SN);
}

void SemanticVisitor::visitSub( Sub* SUB )
{
	SUB->getLeft()->accept(*this);
	SUB->getRight()->accept(*this);

	etype left = stk.top();
	stk.pop();
	etype right = stk.top();
	stk.pop();

	if( left != SN || right != SN )
	{
		cerr << "Error(line " << SUB->getLineNum() << "): Cannot subtract non numeric type." << endl;
		exit(1);
	}
	stk.push(SN);
}

void SemanticVisitor::visitMul( Mul* MUL )
{
	MUL->getLeft()->accept(*this);
	MUL->getRight()->accept(*this);

	etype left = stk.top();
	stk.pop();
	etype right = stk.top();
	stk.pop();

	if( left != SN || right != SN )
	{
		cerr << "Error(line " << MUL->getLineNum() << "): Cannot multiply non numeric type." << endl;
		exit(1);
	}
	stk.push(SN);
}

void SemanticVisitor::visitDiv( Div* DIV )
{
	DIV->getLeft()->accept(*this);
	DIV->getRight()->accept(*this);

	etype left = stk.top();
	stk.pop();
	etype right = stk.top();
	stk.pop();

	if( left != SN || right != SN )
	{
		cerr << "Error(line " << DIV->getLineNum() << "): Cannot divide non numeric type." << endl;
		exit(1);
	}
	stk.push(SN);
}

void SemanticVisitor::visitMod( Mod* MOD )
{
	MOD->getLeft()->accept(*this);
	MOD->getRight()->accept(*this);

	etype left = stk.top();
	stk.pop();
	etype right = stk.top();
	stk.pop();

	if( left != SN || right != SN )
	{
		cerr << "Error(line " << MOD->getLineNum() << "): Cannot take mod of non numeric type." << endl;
		exit(1);
	}
	stk.push(SN);
}

void SemanticVisitor::visitEqu( Equ* EQU )
{
	EQU->getLeft()->accept(*this);
	EQU->getRight()->accept(*this);

	etype right = stk.top();
	stk.pop();
	etype left = stk.top();
	stk.pop();

	if( left != right && left != TYPE_LESS )
	{
		cerr << "Error(line " << EQU->getLineNum() << "): Cannot assign different types" << endl;
		exit(1);
	}
	stk.push(left);
}

void SemanticVisitor::visitDoubleEqual( DoubleEqual* DE )
{
	DE->getLeft()->accept(*this);
	DE->getRight()->accept(*this);

	etype right = stk.top();
	stk.pop();
	etype left = stk.top();
	stk.pop();

	if( left != SN && left != SC )
	{
		cerr << "Error(line " << DE->getLineNum() << "): Cannot equate non scalar types." << endl;
		exit(1);
	}
	if( left != right )
	{
		cerr << "Error(line " << DE->getLineNum() << "): Cannot equate left and right operands of different type." << endl;
		exit(1);
	}
	stk.push(SN);
}

void SemanticVisitor::visitNotEqual( NotEqual* NE )
{
	NE->getLeft()->accept(*this);
	NE->getRight()->accept(*this);

	etype right = stk.top();
	stk.pop();
	etype left = stk.top();
	stk.pop();

	if( left != SN && left != SC )
	{
		cerr << "Error(line " << NE->getLineNum() << "): Cannot inequate non scalar types." << endl;
		exit(1);
	}
	if( left != right )
	{
		cerr << "Error(line " << NE->getLineNum() << "): Cannot inequate left and right operands of different type." << endl;
		exit(1);
	}
	stk.push(SN);
}

void SemanticVisitor::visitLess( Less* LESS )
{
	LESS->getLeft()->accept(*this);
	LESS->getRight()->accept(*this);

	etype right = stk.top();
	stk.pop();
	etype left = stk.top();
	stk.pop();

	if( left != SN && left != SC )
	{
		cerr << "Error(line " << LESS->getLineNum() << "): Cannot check for less of non scalar types." << endl;
		exit(1);
	}
	if( left != right )
	{
		cerr << "Error(line " << LESS->getLineNum() << "): Cannot check for less of  left and right operands of different type." << endl;
		exit(1);
	}
	stk.push(SN);
}

void SemanticVisitor::visitGreater( Greater* GR )
{
	GR->getLeft()->accept(*this);
	GR->getRight()->accept(*this);

	etype right = stk.top();
	stk.pop();
	etype left = stk.top();
	stk.pop();

	if( left != SN && left != SC )
	{
		cerr << "Error(line " << GR->getLineNum() << "): Cannot check for greater of non scalar types." << endl;
		exit(1);
	}
	if( left != right )
	{
		cerr << "Error(line " << GR->getLineNum() << "): Cannot check for greater of left and right operands of different type." << endl;
		exit(1);
	}
	stk.push(SN);
}

void SemanticVisitor::visitLessEqual( LessEqual* LE )
{
	LE->getLeft()->accept(*this);
	LE->getRight()->accept(*this);

	etype right = stk.top();
	stk.pop();
	etype left = stk.top();
	stk.pop();

	if( left != SN && left != SC )
	{
		cerr << "Error(line " << LE->getLineNum() << "): Cannot less equal non scalar types." << endl;
		exit(1);
	}
	if( left != right )
	{
		cerr << "Error(line " << LE->getLineNum() << "): Cannot less equal left and right operands of different type." << endl;
		exit(1);
	}
	stk.push(SN);
}

void SemanticVisitor::visitGreaterEqual( GreaterEqual* GE )
{
	GE->getLeft()->accept(*this);
	GE->getRight()->accept(*this);

	etype right = stk.top();
	stk.pop();
	etype left = stk.top();
	stk.pop();

	if( left != SN && left != SC )
	{
		cerr << "Error(line " << GE->getLineNum() << "): Cannot greater equal non scalar types." << endl;
		exit(1);
	}
	if( left != right )
	{
		cerr << "Error(line " << GE->getLineNum() << "): Cannot greater equal left and right operands of different type." << endl;
		exit(1);
	}
	stk.push(SN);
}

void SemanticVisitor::visitAnd( And* AND )
{
	AND->getLeft()->accept(*this);
	AND->getRight()->accept(*this);

	etype right = stk.top();
	stk.pop();
	etype left = stk.top();
	stk.pop();

	if( left != SN )
	{
		cerr << "Error(line " << AND->getLineNum() << "): Cannot and non scalar types." << endl;
		exit(1);
	}
	if( left != right )
	{
		cerr << "Error(line " << AND->getLineNum() << "): Cannot and left and right operands of different type." << endl;
		exit(1);
	}
	stk.push(SN);
}

void SemanticVisitor::visitOr( Or* OR )
{
	OR->getLeft()->accept(*this);
	OR->getRight()->accept(*this);

	etype right = stk.top();
	stk.pop();
	etype left = stk.top();
	stk.pop();

	if( left != SN )
	{
		cerr << "Error(line " << OR->getLineNum() << "): Cannot or non scalar types." << endl;
		exit(1);
	}
	if( left != right )
	{
		cerr << "Error(line " << OR->getLineNum() << "): Cannot or left and right operands of different type." << endl;
		exit(1);
	}
	stk.push(SN);
}

void SemanticVisitor::visitWhile( While* WH )
{
	WH->getCondition()->accept(*this);
	if( stk.top() != SN )
	{
		cerr << "Error(line " << WH->getLineNum() << "): While condition must evaluate to a scalar number." << endl;
		exit(1);
	}
	stk.pop();

	WH->getBlock()->accept(*this);
}

void SemanticVisitor::visitIf( If* IF )
{
	IF->getCondition()->accept(*this);
	if( stk.top() != SN )
	{
		cerr << "Error(line " << IF->getLineNum() << "): If condition must evaluate to a scalar number." << endl;
		exit(1);
	}
	stk.pop();

	const std::vector<Node*> blocks = IF->getBlocks();
	for( std::vector<Node*>::const_iterator it = blocks.begin(); it != blocks.end(); it++ )
		(*it)->accept(*this);
}

void SemanticVisitor::visitFunctionCall( FunctionCall * FC )
{
	// Call accept on ArgNode
	FC->getArgNode()->accept(*this);
	// Push on function's return value type
	stk.push( sym->GetEType(FC->getId()) );
}

void SemanticVisitor::visitFunctionDefinition( FunctionDefinition * FD )
{
	// Call accept on Children
	FD->getParamNode()->accept(*this);
	FD->getCodeBlock()->accept(*this);
}

void SemanticVisitor::visitReturn( Return * R )
{
	// Get Funtion Etype from type
	int typeFunc = R->getFunc();
	etype func = sym->GetEType(typeFunc);
	
	// Call accept on children
	R->getExpr()->accept(*this);

	// Check that the expr type matches the function's return type
	etype ret = stk.top();

	if( ret != func )
	{
		cerr << "Error(line " << R->getLineNum() << "): Return type does not match function return type." << endl;
		exit(1);
	}
	// Pop expr to return off of the stack
	stk.pop();
}

void SemanticVisitor::visitParamNode( ParamNode * PN )
{
	// Call accept on children
	std::list<Node*> Params = PN->getParamList();
	for(list<Node*>::iterator it = Params.begin(); it != Params.end(); it++ )
	{
		(*it)->accept(*this);
	}
}

void SemanticVisitor::visitArgNode( ArgNode * AN )
{
	// Check that the number of arguments passed in match the number of parameters required
	int typeFunc = AN->getFunc();
	std::list<int> func = sym->getParameters(typeFunc);
	std::list<Node*> args = AN->getArgList();

	if( args.size() != func.size() )
	{
		cerr << "Error(line " << AN->getLineNum() << "): Number of arguments does not match function's number of parameters." << endl;
		exit(1);
	}

	// Call accept on children and
	// Check that the passed in value matches the corresponding parameter type
	list<int>::iterator itFunc = func.begin();
	//list<int>::reverse_iterator ritFunc = func.rbegin();
	list<Node*>::iterator it = args.begin();
	for( ; it != args.end(); itFunc++, it++ )
	{
		(*it)->accept(*this);
		etype temp = stk.top();
		stk.pop();
		etype param = sym->GetEType(*itFunc);
		if( temp != param )
		{
			cerr << "Error(line " << AN->getLineNum() << "): Argument's type does not match the function's parameter type." << endl;
			exit(1);
		}
	}
}

